/*********************************************************
          File Name:shared_ptr.h
          Author: Abby Cin
          Mail: abbytsing@gmail.com
          Created Time: Tue 15 Dec 2015 07:13:34 PM CST
**********************************************************/

#ifndef SHARED_PTR_H_
#define SHARED_PTR_H_

#include <functional>
#include <iostream>

namespace nm
{
        struct Deleter
        {
                template<typename D> void operator()(D *d)
                {
                        if(d)
                        {
                                delete d;
                                d = nullptr;
                        }
                }
        };

        template<typename T> class shared_ptr;

        template<typename T>
        void swap(shared_ptr<T> &lhs, shared_ptr<T> &rhs)
        {
                using std::swap;
                swap(lhs.ptr, rhs.ptr);
                swap(lhs.ref_count, rhs.ref_count);
                swap(lhs.deleter, rhs.deleter);
        }

        template<typename T> class shared_ptr final
        {
                private:
                        T *ptr;
                        long *ref_count;
                        std::function<void (T*)> deleter;
                        
                        void delete_if_count_zero()
                        {
                                if(ref_count)
                                {
                                        if(--*ref_count == 0)
                                        {
                                                delete ref_count;
                                                deleter(ptr);
                                                ptr = nullptr;
                                                ref_count = nullptr;
                                        }
                                }
                        }
                public:
                        shared_ptr()
                        {
                                ptr = nullptr;
                                ref_count = nullptr;
                                deleter = Deleter();
                        }

                        shared_ptr(T *n)
                        {
                                ptr = n;
                                if(n)
                                        ref_count = new long(1);
                                else
                                        ref_count = nullptr;
                                deleter = Deleter();
                        }

                        shared_ptr(T *n, std::function<void (T*)> f)
                        {
                                ptr = n;
                                if(n)
                                        ref_count = new long(1);
                                else
                                        ref_count = nullptr;
                                deleter = f;
                        }

                        shared_ptr(const shared_ptr &n)
                        {
                                ptr = n.ptr;
                                ref_count = n.ref_count;
                                deleter = n.deleter;
                                if(ref_count)
                                        ++*ref_count;
                        }

                        shared_ptr(shared_ptr &&n)
                        {
                                nm::swap(*this, n);
                                n.delete_if_count_zero();
                        }

                        shared_ptr &operator=(const shared_ptr n)
                        {
                                this->delete_if_count_zero();
                                ptr = n.ptr;
                                ref_count = n.ref_count;
                                deleter = n.deleter;

                                ++*ref_count;
                        }

                        shared_ptr &operator=(shared_ptr &&n)
                        {
                                this->delete_if_count_zero();
                                nm::swap(*this, n);
                                n.ptr = nullptr;
                                n.ref_count = nullptr;
                                n.deleter = nullptr;
                        }

                        ~shared_ptr()
                        {
                                delete_if_count_zero();
                        }

                        T &operator*() const    // return a reference of `dereferenced` ptr
                        {
                                return *ptr;
                        }

                        T *operator->() const
                        {
                                return ptr;
                        }

                        void reset() noexcept
                        {
                                delete_if_count_zero();
                        }

                        void reset(T *p)
                        {
                                if(p != nullptr && ptr != p)
                                {
                                        delete_if_count_zero();
                                        ptr = p;
                                        ref_count = new long(1);
                                }
                        }

                        void reset(T *p, const std::function<void (T*)> &d)
                        {
                                reset(p);
                                deleter = d;
                        }

                        void swap(shared_ptr &rhs) noexcept
                        {
                                nm::swap(*this, rhs);
                        }

                        T *get() const noexcept
                        {
                                return ptr;
                        }

                        long use_count() const
                        {
                                if(ref_count)
                                        return *ref_count;
                                else
                                        return 0;
                        }

                        // a shared pointer use `shared_ptr()` is not valid, thus, not unique
                        bool unique() const
                        {
                                if(ref_count)
                                        return *ref_count == 1;
                                else
                                        return false;
                        }

                        // a valid shared pointer's ref_count != nullptr
                        explicit operator bool() const
                        {
                                return ref_count != nullptr;
                        }
        };
}

#endif
